/****************************************************************************
 * Copyright (c) 2008 Jan S. Rellermeyer, and others
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *     Jan S. Rellermeyer - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/

package org.eclipse.ecf.internal.provider.r_osgi;

import ch.ethz.iks.r_osgi.*;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.ecf.core.*;
import org.eclipse.ecf.core.events.*;
import org.eclipse.ecf.core.identity.*;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.EndpointDescription;
import org.eclipse.ecf.provider.r_osgi.identity.*;
import org.eclipse.ecf.remoteservice.*;
import org.eclipse.ecf.remoteservice.events.IRemoteServiceRegisteredEvent;
import org.eclipse.ecf.remoteservice.events.IRemoteServiceUnregisteredEvent;
import org.eclipse.equinox.concurrent.future.*;
import org.osgi.framework.*;
import org.osgi.framework.Constants;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.service.remoteserviceadmin.RemoteConstants;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

/**
 * The R-OSGi remote service container adapter. Implements the adapter and the
 * container interface.
 * 
 * @author Jan S. Rellermeyer, ETH Zurich
 */
class R_OSGiRemoteServiceContainer implements IOSGiRemoteServiceContainerAdapter, IRemoteServiceContainerAdapter, IContainer, RemoteServiceListener, IRSAConsumerContainerAdapter {

	// the bundle context.
	private BundleContext context;

	// the R-OSGi remote service instance.
	private RemoteOSGiService remoteService;

	// the list of subscribed container listeners.
	private final List containerListeners = new ArrayList(0);

	// the ID of this container.
	R_OSGiID containerID;

	// the ID of the remote peer to which the container is connected to, or
	// null, if not yet connected.
	private R_OSGiID connectedID;

	// tracks the lifecycle of remote services.
	private ServiceTracker remoteServicesTracker;

	// service reference -> service registration
	private Map remoteServicesRegs = new HashMap(0);

	// the map of remote service listeners. Maps the listener to the service
	// registration of the internal R-OSGi remote service listener service.
	private Map remoteServiceListeners = new HashMap(0);

	// Connect context to use for connect calls
	private IConnectContext connectContext;

	// New system property to allow the per-transport exposure of remote services to be defeated.
	private boolean exposeRemoteServicesOnAllTransports = Boolean.parseBoolean(System.getProperty("org.eclipse.ecf.internal.provider.r_osgi.exposeRemoteServicesOnAllTransports", "false")); //$NON-NLS-1$ //$NON-NLS-2$

	public R_OSGiRemoteServiceContainer(RemoteOSGiService service, final ID containerID) throws IDCreateException {
		Assert.isNotNull(service);
		Assert.isNotNull(containerID);
		context = Activator.getDefault().getContext();
		remoteService = service;
		if (containerID instanceof StringID) {
			this.containerID = createR_OSGiID(((StringID) containerID).getName());
		} else if (containerID instanceof R_OSGiID) {
			this.containerID = (R_OSGiID) containerID;
		} else {
			throw new IDCreateException("Incompatible ID " + containerID); //$NON-NLS-1$
		}
		startRegTracker();
	}

	private void startRegTracker() {
		try {
			final String filter = "(" + org.eclipse.ecf.remoteservice.Constants.SERVICE_CONTAINER_ID + "=" + containerID + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			remoteServicesTracker = new ServiceTracker(context, context.createFilter(filter), new ServiceTrackerCustomizer() {

				public Object addingService(ServiceReference reference) {
					return reference;
				}

				public void modifiedService(ServiceReference reference, Object service) {
					// service got modified
					return;
				}

				public void removedService(ServiceReference reference, Object service) {
					// service got removed
				}
			});
			remoteServicesTracker.open();
		} catch (InvalidSyntaxException e) {
			e.printStackTrace();
		}
	}

	/**
	 * add a remote service listener. This method accepts an ECF remote service
	 * listener and registers a R-OSGi listener service as an adapter.
	 * 
	 * @param listener
	 *            the ECF remote service listener.
	 * 
	 * @see org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter#addRemoteServiceListener(org.eclipse.ecf.remoteservice.IRemoteServiceListener)
	 */
	public void addRemoteServiceListener(final IRemoteServiceListener listener) {
		Assert.isNotNull(listener);

		final RemoteServiceListener l = new RemoteServiceListener() {
			public void remoteServiceEvent(final RemoteServiceEvent event) {
				switch (event.getType()) {
					case RemoteServiceEvent.REGISTERED :
						listener.handleServiceEvent(new IRemoteServiceRegisteredEvent() {

							IRemoteServiceReference reference = new RemoteServiceReferenceImpl(createRemoteServiceID(event.getRemoteReference()), event.getRemoteReference());

							public String[] getClazzes() {
								return event.getRemoteReference().getServiceInterfaces();
							}

							public ID getContainerID() {
								return getReference().getContainerID();
							}

							public IRemoteServiceReference getReference() {
								return reference;
							}

							public String toString() {
								return "RemoteServiceRegisteredEvent(" + containerID + "," + getReference(); //$NON-NLS-1$ //$NON-NLS-2$
							}

							public ID getLocalContainerID() {
								return getID();
							}
						});
						return;
					case RemoteServiceEvent.UNREGISTERING :
						listener.handleServiceEvent(new IRemoteServiceUnregisteredEvent() {
							IRemoteServiceReference reference = new RemoteServiceReferenceImpl(createRemoteServiceID(event.getRemoteReference()), event.getRemoteReference());

							public String[] getClazzes() {
								return event.getRemoteReference().getServiceInterfaces();
							}

							public ID getContainerID() {
								return containerID;
							}

							public IRemoteServiceReference getReference() {
								return reference;
							}

							public String toString() {
								return "RemoteServiceUnregisteredEvent(" + containerID + "," + getReference(); //$NON-NLS-1$ //$NON-NLS-2$
							}

							public ID getLocalContainerID() {
								return getID();
							}
						});
						return;
				}
			}
		};
		// register the listener as a service (whiteboard pattern)
		final ServiceRegistration reg = context.registerService(RemoteServiceListener.class.getName(), l, null);
		// keep track of the listener so that it can be removed when requested.
		remoteServiceListeners.put(listener, reg);
	}

	/**
	 * get a remote service by its remote service reference.
	 * 
	 * @param reference
	 *            the remote service reference.
	 * @return the IRemoteService object, encapsulating the service proxy and
	 *         additional methods for asynchronous and other access methods.
	 * @see org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter#getRemoteService(org.eclipse.ecf.remoteservice.IRemoteServiceReference)
	 */
	public IRemoteService getRemoteService(final IRemoteServiceReference reference) {
		Assert.isNotNull(reference);
		if (!(reference instanceof RemoteServiceReferenceImpl))
			return null;
		final RemoteServiceReferenceImpl impl = (RemoteServiceReferenceImpl) reference;
		Object rs = remoteService.getRemoteService(impl.getR_OSGiServiceReference());
		if (rs == null)
			return null;
		final RemoteServiceImpl rsImpl = new RemoteServiceImpl(impl, rs);
		synchronized (refToImplMap) {
			List remoteServiceImplList = (List) refToImplMap.get(reference);
			if (remoteServiceImplList == null)
				remoteServiceImplList = new ArrayList();
			remoteServiceImplList.add(rsImpl);
			refToImplMap.put(reference, remoteServiceImplList);
		}
		return rsImpl;
	}

	// <IRemoteServiceReference,List<RemoteServiceImpl>>
	private Map refToImplMap = new HashMap();

	public IRemoteServiceReference[] getRemoteServiceReferences(ID target, ID[] idFilter, String clazz, String filter) throws InvalidSyntaxException, ContainerConnectException {
		// r-osgi does not support the idFilter, since it does not support pub/sub
		return getRemoteServiceReferences(target, clazz, filter);
	}

	/**
	 * get remote service references.
	 * 
	 * @param idFilter
	 *            a filter that limits the results to services registered by one
	 *            of the IDs.
	 * @param clazz
	 *            the interface name of the remote service.
	 * @param filter
	 *            LDAP filter string that is matched against the service
	 *            properties.
	 * @return the matching remote service references.
	 * @throws InvalidSyntaxException
	 * @see org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter#getRemoteServiceReferences(org.eclipse.ecf.core.identity.ID[],
	 *      java.lang.String, java.lang.String)
	 */
	public IRemoteServiceReference[] getRemoteServiceReferences(final ID[] idFilter, final String clazz, final String filter) throws InvalidSyntaxException {
		// r-osgi does not support the idFilter, since it does not support pub/sub
		IRemoteFilter remoteFilter = (filter == null) ? null : createRemoteFilter(filter);
		List results = getRemoteServiceReferencesConnected(clazz, remoteFilter);
		if (results == null)
			return null;
		return (IRemoteServiceReference[]) results.toArray(new IRemoteServiceReference[] {});
	}

	public IRemoteServiceReference[] getRemoteServiceReferences(ID targetID, String clazz, String filter) throws InvalidSyntaxException, ContainerConnectException {
		if (clazz == null)
			return null;
		IRemoteFilter remoteFilter = (filter == null) ? null : createRemoteFilter(filter);
		synchronized (this) {
			List results = new ArrayList();
			if (getConnectedID() == null)
				connect(targetID, connectContext);
			results = getRemoteServiceReferencesConnected(clazz, remoteFilter);
			if (results == null || results.size() == 0)
				return null;
			return (IRemoteServiceReference[]) results.toArray(new IRemoteServiceReference[] {});
		}
	}

	public IRemoteServiceReference[] getAllRemoteServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
		List results = new ArrayList();
		// Get remote service references from locally registered services first
		synchronized (remoteServicesRegs) {
			for (Iterator i1 = remoteServicesRegs.keySet().iterator(); i1.hasNext();) {
				ServiceReference ref = (ServiceReference) i1.next();
				Dictionary refProperties = prepareProperties(ref);
				if (clazz == null) {
					results.add(createLocalRemoteServiceReference(ref));
				} else {
					IRemoteFilter rf = createRemoteFilter(filter != null ? "(&" + filter + "(" //$NON-NLS-1$ //$NON-NLS-2$
							+ Constants.OBJECTCLASS + "=" + clazz + "))" //$NON-NLS-1$//$NON-NLS-2$
							: "(" //$NON-NLS-1$
									+ Constants.OBJECTCLASS + "=" + clazz + ")"); //$NON-NLS-1$//$NON-NLS-2$
					if (rf.match(refProperties)) {
						results.add(createLocalRemoteServiceReference(ref));
					}
				}

			}
		}
		IRemoteFilter remoteFilter = (filter == null) ? null : createRemoteFilter(filter);
		if (getConnectedID() != null) {
			final RemoteServiceReference[] rrefs = remoteService.getRemoteServiceReferences(connectedID.getURI(), clazz, remoteFilter);
			if (rrefs != null)
				for (int i = 0; i < rrefs.length; i++)
					results.add(getCachedRemoteServiceReference(rrefs[i]));
		}
		return (IRemoteServiceReference[]) results.toArray(new IRemoteServiceReference[] {});
	}

	private IRemoteServiceReference createLocalRemoteServiceReference(ServiceReference ref) {
		return new LocalRemoteServiceReferenceImpl(createRemoteServiceID(containerID, (Long) ref.getProperty(Constants.SERVICE_ID)), ref);
	}

	private synchronized List getRemoteServiceReferencesConnected(final String clazz, IRemoteFilter filter) {
		List results = new ArrayList();
		if (connectedID == null) {
			try {
				IRemoteServiceReference[] refs = getAllRemoteServiceReferences(clazz, (filter == null) ? null : filter.toString());
				if (refs == null)
					return results;
				for (int i = 0; i < refs.length; i++)
					results.add(refs[i]);
			} catch (InvalidSyntaxException e) {
				// can't happen
			}
		} else {
			RemoteServiceReference[] rrefs = remoteService.getRemoteServiceReferences(connectedID.getURI(), clazz, filter);
			if (rrefs == null)
				return results;
			for (int i = 0; i < rrefs.length; i++)
				results.add(getCachedRemoteServiceReference(rrefs[i]));
		}
		return results;
	}

	private Map cachedRemoteServiceReferences = new HashMap();

	private IRemoteServiceReference getCachedRemoteServiceReference(RemoteServiceReference rref) {
		IRemoteServiceReference result = null;
		synchronized (cachedRemoteServiceReferences) {
			result = (IRemoteServiceReference) cachedRemoteServiceReferences.get(rref);
			if (result == null) {
				result = new RemoteServiceReferenceImpl(createRemoteServiceID(rref), rref);
				cachedRemoteServiceReferences.put(rref, result);
			}
		}
		return result;
	}

	IRemoteServiceID createRemoteServiceID(R_OSGiID cID, Long l) {
		return (IRemoteServiceID) IDFactory.getDefault().createID(getRemoteServiceNamespace(), new Object[] {cID, l});
	}

	IRemoteServiceID createRemoteServiceID(RemoteServiceReference rref) {
		URI uri = rref.getURI();
		String fragmentString = "#" + uri.getFragment(); //$NON-NLS-1$
		String uriStr = uri.toString();
		int fragmentLoc = uriStr.indexOf(fragmentString);
		if (fragmentLoc != -1) {
			uriStr = uriStr.substring(0, fragmentLoc);
		}
		return createRemoteServiceID(createR_OSGiID(uriStr), (Long) rref.getProperty(Constants.SERVICE_ID));
	}

	private R_OSGiID createR_OSGiID(String uriStr) {
		return R_OSGiContainerInstantiator.createR_OSGiID(getConnectNamespace(), uriStr);
	}

	/**
	 * register a service object as a remote service.
	 * 
	 * @param clazzes
	 *            the names of the service interfaces under which the service
	 *            will be registered.
	 * @param service
	 *            the service object.
	 * @param properties
	 *            the service properties.
	 * @see org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter#registerRemoteService(java.lang.String[],
	 *      java.lang.Object, java.util.Dictionary)
	 */
	public IRemoteServiceRegistration registerRemoteService(final String[] clazzes, final Object service, final Dictionary properties) {
		return registerRemoteService(clazzes, service, properties, context);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.ecf.remoteservice.IOSGiRemoteServiceContainerAdapter#registerRemoteService(java.lang.String[], org.osgi.framework.ServiceReference, java.util.Dictionary)
	 */
	public IRemoteServiceRegistration registerRemoteService(final String[] clazzes, final ServiceReference aServiceReference, final Dictionary properties) {
		final Object service = context.getService(aServiceReference);
		final BundleContext bundleContext = aServiceReference.getBundle().getBundleContext();
		return registerRemoteService(clazzes, service, properties, bundleContext);
	}

	private String getPackageName(String className) {
		int lastDotIndex = className.lastIndexOf("."); //$NON-NLS-1$
		if (lastDotIndex == -1)
			return ""; //$NON-NLS-1$
		return className.substring(0, lastDotIndex);
	}

	private Version getPackageVersion(final Object service, String serviceInterface, String packageName) {
		List<Class> interfaces = new ArrayList<Class>();
		Class<?> serviceClass = service.getClass();
		while (!serviceClass.equals(Object.class)) {
			interfaces.addAll(Arrays.asList(serviceClass.getInterfaces()));
			serviceClass = serviceClass.getSuperclass();
		}
		Class[] interfaceClasses = interfaces.toArray(new Class[interfaces.size()]);

		if (interfaceClasses == null)
			return null;
		Class interfaceClass = null;
		for (int i = 0; i < interfaceClasses.length; i++)
			if (interfaceClasses[i].getName().equals(serviceInterface))
				interfaceClass = interfaceClasses[i];
		if (interfaceClass == null)
			return null;
		Bundle providingBundle = FrameworkUtil.getBundle(interfaceClass);
		if (providingBundle == null)
			return null;
		return getVersionForPackage(providingBundle, packageName);
	}

	private Version getVersionForMatchingCapability(String packageName, BundleCapability capability) {
		// If it's a package namespace (Import-Package)
		Map<String, Object> attributes = capability.getAttributes();
		// Then we get the package attribute
		String p = (String) attributes.get(BundleRevision.PACKAGE_NAMESPACE);
		// And compare it to the package name
		if (p != null && packageName.equals(p))
			return (Version) attributes.get(Constants.VERSION_ATTRIBUTE);
		return null;
	}

	private Version getVersionForPackage(final Bundle providingBundle, String packageName) {
		Version result = null;
		BundleRevision providingBundleRevision = AccessController.doPrivileged(new PrivilegedAction<BundleRevision>() {
			public BundleRevision run() {
				return providingBundle.adapt(BundleRevision.class);
			}
		});
		if (providingBundleRevision == null)
			return null;
		List<BundleCapability> providerCapabilities = providingBundleRevision.getDeclaredCapabilities(BundleRevision.PACKAGE_NAMESPACE);
		for (BundleCapability c : providerCapabilities) {
			result = getVersionForMatchingCapability(packageName, c);
			if (result != null)
				return result;
		}
		return result;
	}

	private IRemoteServiceRegistration registerRemoteService(final String[] clazzes, final Object service, final Dictionary properties, final BundleContext aContext) {
		if (containerID == null) {
			throw new IllegalStateException("Container is not connected"); //$NON-NLS-1$
		}

		final Dictionary props = properties == null ? new Hashtable() : clone(properties);

		R_OSGiNamespace ns = (R_OSGiNamespace) containerID.getNamespace();
		// add the hint property for R-OSGi that this service is intended to be
		// accessed remotely.
		Object rosgiRegistrationValue = Boolean.TRUE;
		if (!exposeRemoteServicesOnAllTransports)
			rosgiRegistrationValue = (ns instanceof R_OSGiWSSNamespace) ? "https" : (ns instanceof R_OSGiWSNamespace) ? "http" : "r-osgi"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$

		props.put(RemoteOSGiService.R_OSGi_REGISTRATION, rosgiRegistrationValue);
		// remove the RFC 119 hint, if present, to avoid loops
		props.remove("osgi.remote.interfaces"); //$NON-NLS-1$
		props.remove("service.exported.interfaces"); //$NON-NLS-1$
		// ECF remote service properties
		// container ID (ID)
		props.put(org.eclipse.ecf.remoteservice.Constants.SERVICE_CONTAINER_ID, containerID);
		// Object classes (String [])
		props.put(org.eclipse.ecf.remoteservice.Constants.OBJECTCLASS, clazzes);
		// service ranking (Integer).  Allow this to be set by user
		Integer serviceRanking = (Integer) props.get(org.eclipse.ecf.remoteservice.Constants.SERVICE_RANKING);
		serviceRanking = (serviceRanking == null) ? Integer.valueOf(0) : serviceRanking;
		props.put(org.eclipse.ecf.remoteservice.Constants.SERVICE_RANKING, serviceRanking);

		for (String clazz : clazzes) {
			// Add osgi-standard remote service version for each package
			String packageName = getPackageName(clazz);
			String packageVersionKey = "endpoint.package.version." + packageName; //$NON-NLS-1$
			Version packageVersion = getPackageVersion(service, clazz, packageName);
			if (packageVersion != null) {
				props.put(packageVersionKey, packageVersion.toString());
			}
		}

		final ServiceRegistration reg = aContext.registerService(clazzes, service, props);
		// Set ECF remote service id property based upon local service property
		reg.setProperties(prepareProperties(reg.getReference()));

		synchronized (remoteServicesRegs) {
			remoteServicesRegs.put(reg.getReference(), reg);
		}
		// Construct a IRemoteServiceID, and provide to new registration impl instance
		return new RemoteServiceRegistrationImpl(service.getClass().getClassLoader(), this, createRemoteServiceID(containerID, (Long) reg.getReference().getProperty(Constants.SERVICE_ID)), reg);
	}

	Dictionary prepareProperties(ServiceReference reference) {
		String[] propKeys = reference.getPropertyKeys();
		Dictionary newDictionary = new Properties();
		for (int i = 0; i < propKeys.length; i++) {
			Object v = reference.getProperty(propKeys[i]);
			newDictionary.put(propKeys[i], v);
			// Make the remote service SERVICE_ID have the same value as OSGi SERVICE_ID
			if (Constants.SERVICE_ID.equals(propKeys[i])) {
				newDictionary.put(org.eclipse.ecf.remoteservice.Constants.SERVICE_ID, v);
			}
		}
		return newDictionary;
	}

	/**
	 * remove a registered remote service listener.
	 * 
	 * @param listener
	 *            the remote service listener.
	 * @see org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter#removeRemoteServiceListener(org.eclipse.ecf.remoteservice.IRemoteServiceListener)
	 */
	public void removeRemoteServiceListener(final IRemoteServiceListener listener) {
		remoteServiceListeners.remove(listener);
	}

	/**
	 * 
	 * @see org.eclipse.ecf.remoteservice.IRemoteServiceContainerAdapter#ungetRemoteService(org.eclipse.ecf.remoteservice.IRemoteServiceReference)
	 */
	public boolean ungetRemoteService(IRemoteServiceReference reference) {
		if (!(reference instanceof RemoteServiceReferenceImpl))
			return false;
		RemoteServiceReferenceImpl impl = (RemoteServiceReferenceImpl) reference;
		RemoteServiceReference rsr = impl.getR_OSGiServiceReference();
		if (!rsr.isActive())
			return false;
		remoteService.ungetRemoteService(rsr);
		synchronized (refToImplMap) {
			List remoteServiceImplList = (List) refToImplMap.remove(reference);
			if (remoteServiceImplList != null) {
				for (Iterator i = remoteServiceImplList.iterator(); i.hasNext();) {
					RemoteServiceImpl rsImpl = (RemoteServiceImpl) i.next();
					if (rsImpl != null)
						rsImpl.dispose();
					i.remove();
				}
			}
		}
		return true;
	}

	/**
	 * returns an adapter for a given class. In this particular case, only the
	 * IRemoteServiceContainerAdapter interface and the IContainer interface are
	 * supported.
	 * 
	 * @param adapter
	 *            the class to adapt to.
	 * @return the adapter or null if the adaptation is not supported.
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(final Class adapter) {
		if (adapter.equals(IRemoteServiceContainerAdapter.class)) {
			return this;
		} else if (adapter.equals(IContainer.class)) {
			return this;
		} else if (adapter.equals(IRSAConsumerContainerAdapter.class)) {
			return this;
		}
		return null;
	}

	// container part

	/**
	 * add a container listener.
	 * 
	 * @param listener
	 *            the container listener.
	 * 
	 * @see org.eclipse.ecf.core.IContainer#addListener(org.eclipse.ecf.core.IContainerListener)
	 */
	public void addListener(final IContainerListener listener) {
		containerListeners.add(listener);
	}

	/**
	 * connect the container to a remote container instance.
	 * 
	 * @param targetID
	 *            the target ID to connect to.
	 * @param connectContext
	 *            the connection context.
	 * @throws ContainerConnectException
	 *             if the connecting fails.
	 * @see org.eclipse.ecf.core.IContainer#connect(org.eclipse.ecf.core.identity.ID,
	 *      org.eclipse.ecf.core.security.IConnectContext)
	 */
	public synchronized void connect(final ID targetID, final IConnectContext cc) throws ContainerConnectException {
		if (targetID == null)
			throw new ContainerConnectException("targetID must not be null"); //$NON-NLS-1$
		// Get and check namespace
		String targetNamespace = targetID.getNamespace().getName();
		if (!(targetNamespace.equals(getConnectNamespace().getName()) || targetNamespace.equals(StringID.class.getName()))) {
			throw new ContainerConnectException("targetID is of incorrect connect namespace for this container"); //$NON-NLS-1$
		}
		// check that we are not connected
		if (connectedID != null) {
			// already connected to this target
			if (connectedID.getName().equals(targetID.getName())) {
				return;
			}
			throw new ContainerConnectException("Container is already connected to " + connectedID); //$NON-NLS-1$
		}

		this.connectContext = cc;

		try {
			final R_OSGiID target = (targetNamespace.equals(StringID.class.getName()) ? createR_OSGiID(((StringID) targetID).getName()) : ((R_OSGiID) targetID));

			fireListeners(new ContainerConnectingEvent(containerID, connectedID));

			final RemoteServiceReference[] refs = doConnect(target);
			if (refs != null)
				for (int i = 0; i < refs.length; i++)
					checkImport(refs[i]);

			connectedID = target;

			startRegTracker();

		} catch (IOException ioe) {
			throw new ContainerConnectException(ioe);
		} catch (IDCreateException e) {
			throw new ContainerConnectException(e);
		}
		fireListeners(new ContainerConnectedEvent(containerID, connectedID));
	}

	private RemoteServiceReference[] doConnect(R_OSGiID targetID) throws IOException {
		return remoteService.connect(targetID.getURI());
	}

	private void doDisconnect(R_OSGiID targetID) {
		synchronized (cachedRemoteServiceReferences) {
			cachedRemoteServiceReferences.clear();
		}
		remoteService.disconnect(targetID.getURI());
	}

	/**
	 * disconnect from the remote container.
	 * 
	 * @see org.eclipse.ecf.core.IContainer#disconnect()
	 */
	public synchronized void disconnect() {
		if (connectedID != null) {
			fireListeners(new ContainerDisconnectingEvent(containerID, connectedID));
			doDisconnect(connectedID);
			connectedID = null;
			fireListeners(new ContainerDisconnectedEvent(containerID, connectedID));
		}

	}

	/**
	 * dispose the container.
	 * 
	 * @see org.eclipse.ecf.core.IContainer#dispose()
	 */
	public void dispose() {
		// unregister remote services
		if (remoteServicesTracker != null) {
			final ServiceReference[] refs = remoteServicesTracker.getServiceReferences();
			if (refs != null) {
				for (int i = 0; i < refs.length; i++) {
					final ServiceRegistration reg = (ServiceRegistration) remoteServicesRegs.get(refs[i]);
					if (reg != null) {
						reg.unregister();
					}
				}
			}
			remoteServicesTracker.close();
			remoteServicesTracker = null;
		}

		// unregister remote listeners
		final ServiceRegistration[] lstn = (ServiceRegistration[]) remoteServiceListeners.values().toArray(new ServiceRegistration[remoteServiceListeners.size()]);
		for (int i = 0; i < lstn.length; i++) {
			try {
				lstn[i].unregister();
			} catch (Throwable t) {
				// ignore and continue
			}
		}

		if (connectedID != null) {
			disconnect();
		}

		synchronized (refToImplMap) {
			refToImplMap.clear();
		}

		fireListeners(new ContainerDisposeEvent(containerID));
		containerListeners.clear();
	}

	/**
	 * get the connect namespace.
	 * 
	 * @return the connect namespace.
	 * @see org.eclipse.ecf.core.IContainer#getConnectNamespace()
	 */
	public Namespace getConnectNamespace() {
		return R_OSGiNamespace.getDefault();
	}

	/**
	 * get the ID to which the container is connected to. Can be
	 * <code>null</code> if the container is not yet connected.
	 * 
	 * @return the ID or null.
	 * @see org.eclipse.ecf.core.IContainer#getConnectedID()
	 */
	public synchronized ID getConnectedID() {
		return connectedID;
	}

	/**
	 * remove a registered container listener.
	 * 
	 * @param listener
	 *            the container listener.
	 * @see org.eclipse.ecf.core.IContainer#removeListener(org.eclipse.ecf.core.IContainerListener)
	 */
	public void removeListener(final IContainerListener listener) {
		containerListeners.remove(listener);
	}

	/**
	 * get the ID of this container instance.
	 * 
	 * @return the ID of this container.
	 * @see org.eclipse.ecf.core.identity.IIdentifiable#getID()
	 */
	public ID getID() {
		return containerID;
	}

	/**
	 * fire the listeners.
	 * 
	 * @param event
	 *            the event.
	 */
	private void fireListeners(final IContainerEvent event) {
		final IContainerListener[] listeners = (IContainerListener[]) containerListeners.toArray(new IContainerListener[containerListeners.size()]);
		new Thread() {
			public void run() {
				for (int i = 0; i < listeners.length; i++) {
					listeners[i].handleEvent(event);
				}
			}
		}.start();
	}

	/**
	 * get the events from R-OSGi received through a RemoteServiceListener
	 * instance.
	 * 
	 * @see ch.ethz.iks.r_osgi.RemoteServiceListener#remoteServiceEvent(ch.ethz.iks.r_osgi.RemoteServiceEvent)
	 */
	public void remoteServiceEvent(final RemoteServiceEvent event) {
		if (event.getType() == RemoteServiceEvent.REGISTERED) {
			checkImport(event.getRemoteReference());
		}
	}

	/**
	 * check if the remote service should be automatically imported by this
	 * container.
	 * 
	 * @param ref
	 *            the remote service reference to check.
	 */
	private void checkImport(final RemoteServiceReference ref) {
		final Object target = ref.getProperty(org.eclipse.ecf.remoteservice.Constants.SERVICE_REGISTRATION_TARGETS);
		if (target instanceof ID && ((ID) target).equals(containerID)) {
			remoteService.getRemoteService(ref);
		} else if (target instanceof ID[]) {
			final ID[] targets = (ID[]) target;
			for (int i = 0; i < targets.length; i++) {
				if (targets[i].equals(containerID)) {
					remoteService.getRemoteService(ref);
				}
			}
		}
	}

	/**
	 * Clone a dictionary instance to avoid modification by the caller of the
	 * registration method.
	 * 
	 * @param props
	 *            the dictionary instance.
	 * @return a clone.
	 */
	private Hashtable clone(final Dictionary props) {
		final Hashtable clone = new Hashtable();
		for (Enumeration e = props.keys(); e.hasMoreElements();) {
			final Object key = e.nextElement();
			clone.put(key, props.get(key));
		}
		return clone;
	}

	public IFuture asyncGetRemoteServiceReferences(final ID[] idFilter, final String clazz, final String filter) {
		IExecutor executor = new ThreadsExecutor();
		return executor.execute(new IProgressRunnable() {
			public Object run(IProgressMonitor monitor) throws Exception {
				return getRemoteServiceReferences(idFilter, clazz, filter);
			}
		}, null);
	}

	public IFuture asyncGetRemoteServiceReferences(final ID target, final String clazz, final String filter) {
		IExecutor executor = new ThreadsExecutor();
		return executor.execute(new IProgressRunnable() {
			public Object run(IProgressMonitor monitor) throws Exception {
				return getRemoteServiceReferences(target, clazz, filter);
			}
		}, null);
	}

	public IFuture asyncGetRemoteServiceReferences(final ID target, final ID[] idFilter, final String clazz, final String filter) {
		IExecutor executor = new ThreadsExecutor();
		return executor.execute(new IProgressRunnable() {
			public Object run(IProgressMonitor monitor) throws Exception {
				return getRemoteServiceReferences(target, idFilter, clazz, filter);
			}
		}, null);
	}

	public Namespace getRemoteServiceNamespace() {
		return IDFactory.getDefault().getNamespaceByName(R_OSGiRemoteServiceNamespace.NAME);
	}

	public IRemoteFilter createRemoteFilter(String filter) throws InvalidSyntaxException {
		return new RemoteFilterImpl(context, filter);
	}

	public IRemoteServiceID getRemoteServiceID(ID containerId, long containerRelativeId) {
		return (IRemoteServiceID) IDFactory.getDefault().createID(getRemoteServiceNamespace(), new Object[] {containerID, new Long(containerRelativeId)});
	}

	public IRemoteServiceReference getRemoteServiceReference(IRemoteServiceID serviceId) {
		if (serviceId == null)
			return null;
		ID cID = serviceId.getContainerID();
		// If the container ID isn't relevant to us we ignore
		if (cID instanceof R_OSGiID) {
			// If it's not the same as who we're connected to, we ignore
			if (cID.equals(getConnectedID())) {
				final String filter = "(" + Constants.SERVICE_ID + "=" + serviceId + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				try {
					// Get remote service references...I imagine this can/would block
					RemoteServiceReference[] refs = remoteService.getRemoteServiceReferences(((R_OSGiID) cID).getURI(), null, context.createFilter(filter));
					// There should be either zero or 1 remote service reference
					if (refs == null || refs.length == 0)
						return null;
					return getCachedRemoteServiceReference(refs[0]);
				} catch (InvalidSyntaxException e) {
					// shouldn't happen as filter better be well formed
					return null;
				}
			}
		}
		return null;
	}

	public void setConnectContextForAuthentication(IConnectContext connectContext) {
		this.connectContext = connectContext;
	}

	public boolean setRemoteServiceCallPolicy(IRemoteServiceCallPolicy policy) {
		// XXX...we need to see if r-OSGi has a means to implement this
		return false;
	}

	void removeRegistration(ServiceRegistration reg) {
		synchronized (remoteServicesRegs) {
			remoteServicesRegs.remove(reg.getReference());
		}
	}

	public IRemoteServiceReference[] importEndpoint(Map<String, Object> endpointDescriptionProperties) throws ContainerConnectException, InvalidSyntaxException {
		EndpointDescription endpointDescription = new EndpointDescription(endpointDescriptionProperties);
		ID endpointContainerID = endpointDescription.getContainerID();
		Assert.isNotNull(endpointContainerID);
		// Get connect target ID. May be null
		ID tID = endpointDescription.getConnectTargetID();
		if (tID == null)
			tID = endpointContainerID;
		final ID targetID = tID;
		return getRemoteServiceReferences(targetID, getIDFilter(endpointDescription, targetID), endpointDescription.getInterfaces().iterator().next(), getRemoteServiceFilter(endpointDescription));
	}

	private String getRemoteServiceFilter(EndpointDescription endpointDescription) {

		String intf = endpointDescription.getInterfaces().iterator().next();
		String packageName = intf.substring(0, intf.lastIndexOf('.'));
		String edRsFilter = endpointDescription.getRemoteServiceFilter();
		// if edRsFilter is explicitly set, then simply use it
		if (edRsFilter == null) {
			Version intfPackageVersion = endpointDescription.getPackageVersion(packageName);
			edRsFilter = "(" + RemoteConstants.ENDPOINT_PACKAGE_VERSION_ + packageName + "=" + intfPackageVersion + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		}
		long rsId = 0;
		// if the ECF remote service id is present in properties, allow it to
		// override
		Long l = endpointDescription.getRemoteServiceId();
		if (l != null)
			rsId = l.longValue();
		// if rsId is still zero, use the endpoint.service.id from
		// endpoint description
		if (rsId == 0)
			rsId = endpointDescription.getServiceId();
		// If it's *still* zero, then just use the raw filter
		if (rsId == 0) {
			// It's not known...so we just return the 'raw' remote service
			// filter
			return edRsFilter;
		}
		// It's a real remote service id...so we return
		StringBuffer result = new StringBuffer("(&(") //$NON-NLS-1$
				.append(org.eclipse.ecf.remoteservice.Constants.SERVICE_ID).append("=").append(rsId).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
		if (edRsFilter != null)
			result.append(edRsFilter);
		result.append(")"); //$NON-NLS-1$
		return result.toString();
	}

	private ID[] getIDFilter(EndpointDescription endpointDescription, ID endpointID) {
		ID[] idFilter = endpointDescription.getIDFilter();
		// If it is null,
		return (idFilter == null) ? new ID[] {endpointID} : idFilter;
	}

}
